﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.Framework.Client;
using Microsoft.TeamFoundation.Server;

namespace TfsGroupMembershipVisualizer
{
    public class TfsMembershipGraphGenerator
    {
        private XElement _nodes;
        private XElement _links;
        private XElement _styles;
        private XElement _categories;
        private XDocument _permissionsTree;
        private IGroupSecurityService _groupSecurityService;
        private ICommonStructureService _structure;
        private readonly List<string> AddedNode = new List<string>();
        private readonly List<string> AddedGroupNode = new List<string>();
        private TfsTeamProjectCollection _collection;
        private readonly XNamespace DgmlNamespace = "http://schemas.microsoft.com/vs/2009/dgml";

        static TfsMembershipGraphGenerator()
        {
        }

        public XDocument GenerateDependencyGraph(Uri collectionUri, string[] projectUris, string iconPath)
        {
            _collection = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(collectionUri);
            _groupSecurityService = _collection.GetService<IGroupSecurityService>();
            _collection.GetService<ISecurityService>();
            _structure = _collection.GetService<ICommonStructureService>();

            //[DefaultCollection]\Project Collection Valid Users
            var validUsers = _groupSecurityService.ReadIdentity(SearchFactor.EveryoneApplicationGroup, null, QueryMembership.Direct);

            //[DefaultCollection]\Project Collection Service Accounts
            var serviceAccounts = _groupSecurityService.ReadIdentity(SearchFactor.ServiceApplicationGroup, null, QueryMembership.Direct);

            //[DefaultCollection]\Project Collection Administrators
            var admin = _groupSecurityService.ReadIdentity(SearchFactor.AdministrativeApplicationGroup, null, QueryMembership.Direct);

            _permissionsTree = CreateTfsPermissionsTree();
            if (_permissionsTree.Root != null)
            {
                _nodes = _permissionsTree.Root.Element(DgmlNamespace + "Nodes");
                _links = _permissionsTree.Root.Element(DgmlNamespace + "Links");
                _categories = _permissionsTree.Root.Element(DgmlNamespace + "Categories");
                _permissionsTree.Root.Element(DgmlNamespace + "Properties");
                _styles = _permissionsTree.Root.Element(DgmlNamespace + "Styles");
            }
            validUsers = _groupSecurityService.ReadIdentity(SearchFactor.Sid, validUsers.Sid, QueryMembership.Direct);
            _nodes.AddNode(validUsers.Sid, "[" + _collection.CatalogNode.Resource.DisplayName + @"]\" + validUsers.DisplayName, "DefaultCollectionGroupCategory", iconPath, null, new Tuple<string, string>("IsCollectionGroup", "True"));
            AddedNode.Add(validUsers.Sid);
            _nodes.AddNode(serviceAccounts.Sid, "[" + _collection.CatalogNode.Resource.DisplayName + @"]\" + serviceAccounts.DisplayName, "DefaultCollectionGroupCategory", iconPath, null, new Tuple<string, string>("IsCollectionGroup", "True"));
            AddedNode.Add(serviceAccounts.Sid);
            _nodes.AddNode(admin.Sid, "[" + _collection.CatalogNode.Resource.DisplayName + @"]\" + admin.DisplayName, "DefaultCollectionGroupCategory", iconPath, null, new Tuple<string, string>("IsCollectionGroup", "True"));
            AddedNode.Add(admin.Sid);

            AddMembers(serviceAccounts, iconPath);
            AddMembers(admin, iconPath);

            foreach (var projectUri in projectUris)
            {
                var icon = iconPath;
                var projectSecurityGroup = _groupSecurityService.ListApplicationGroups(projectUri);

                foreach (var identity in projectSecurityGroup)
                {
                    var nodeName = identity.DisplayName;

                    var project = _structure.GetProject(identity.Domain);
                    if (project != null)
                    {
                        string container = project.Name + "Group";
                        if (!AddedGroupNode.Contains(project.Uri))
                        {
                            AddedGroupNode.Add(project.Uri);
                            _nodes.AddNode(project.Name + "Group", project.Name, null, null, "Expanded", new Tuple<string, string>("IsProjectGroup", "True"));
                        }
                        const string categoryName = "ProjectGroupSecurity";
                        nodeName = "[" + project.Name + @"]\" + nodeName;

                        _nodes.AddNode(identity.Sid, nodeName, categoryName, icon);
                        _links.AddLink(container, identity.Sid, "Contains");
                        AddedNode.Add(identity.Sid);
                    }

                    AddMembers(identity, iconPath);
                    AddMembersOf(identity, iconPath);
                }
            }

            var containerGroup = _collection.CatalogNode.Resource.DisplayName + "Group";
            _links.AddLink(containerGroup, validUsers.Sid, "Contains");
            _links.AddLink(containerGroup, serviceAccounts.Sid, "Contains");
            _links.AddLink(containerGroup, admin.Sid, "Contains");

            _categories.AddCategory("CategoryMember", "Member");
            _categories.AddCategory("DefaultCollectionGroupCategory", "Default Collection Group");
            _categories.AddCategory("TeamFoundationGroupCategory", "Team Foundation Group");
            _categories.AddCategory("ProjectGroupSecurity", "Project Groups");
            _categories.AddCategory("DomainGroup", "Domain Groups");
            _categories.AddCategory("Contains", "Contains", "False", "True", "Contained By", "True", "Contains");

            var style = _styles.AddStyle("Default Collection Groups");
            style.AddStyleCondition("HasCategory('DefaultCollectionGroupCategory')");
            style.AddStyleSetterProperty("Background", "#FF008040");

            style = _styles.AddStyle("Project Groups");
            style.AddStyleCondition("HasCategory('ProjectGroupSecurity')");
            style.AddStyleSetterProperty("Background", "#FF0080C0");

            style = _styles.AddStyle("Team Foundation Group");
            style.AddStyleCondition("HasCategory('TeamFoundationGroupCategory')");
            style.AddStyleSetterProperty("Background", "#FFC0A000");
            _styles.Add(style);

            style = _styles.AddStyle("Domain Groups");
            style.AddStyleCondition("HasCategory('DomainGroup')");
            style.AddStyleSetterProperty("Background", "#FF7F006E");
            _styles.Add(style);

            return _permissionsTree;
        }

        private void AddMembersOf(Identity identity, string iconPath)
        {
            identity = _groupSecurityService.ReadIdentity(SearchFactor.Sid, identity.Sid, QueryMembership.Direct);

            foreach (var memberOf in identity.MemberOf)
            {
                var group = _groupSecurityService.ReadIdentity(SearchFactor.Sid, memberOf, QueryMembership.Direct);
                string containerGroup = null;
                var nodeName = identity.DisplayName;
                var categoryName = "DefaultCollectionGroupCategory";
                var icon = iconPath;

                if (!string.IsNullOrEmpty(identity.Domain) && identity.Type == IdentityType.ApplicationGroup && identity.Domain.Contains("Framework"))
                {
                    nodeName = @"[TEAM FOUNDATION]\" + nodeName;
                    containerGroup = "TeamFoundationGroup";
                    categoryName = "TeamFoundationGroupCategory";
                    if (!AddedGroupNode.Contains(containerGroup))
                    {
                        AddedGroupNode.Add(containerGroup);
                        _nodes.AddNode(containerGroup, "Team Foundation", null, null, "Expanded");
                    }
                }
                else if (identity.Type == IdentityType.WindowsGroup || identity.Type == IdentityType.WindowsUser)
                {
                    categoryName = "DomainGroup";
                    nodeName = identity.Domain + @"\" + identity.DisplayName;
                    icon = null;
                }

                if (identity.Type == IdentityType.ApplicationGroup && string.IsNullOrEmpty(identity.Domain))
                {
                    nodeName = "[" + _collection.CatalogNode.Resource.DisplayName + @"]\" + identity.DisplayName;
                    containerGroup = _collection.CatalogNode.Resource.DisplayName + "Group";
                    if (!AddedGroupNode.Contains(_collection.CatalogNode.Resource.DisplayName))
                    {
                        AddedGroupNode.Add(_collection.CatalogNode.Resource.DisplayName);
                        _nodes.AddNode(_collection.CatalogNode.Resource.DisplayName + "Group", _collection.CatalogNode.Resource.DisplayName, null, null, "Expanded");
                    }
                }
                
                if (!AddedNode.Contains(identity.Sid))
                {
                    _nodes.AddNode(identity.Sid, nodeName, categoryName, icon);
                    AddedNode.Add(identity.Sid);
                }

                _links.AddLink(group.Sid, identity.Sid, "CategoryMember");

                if (!string.IsNullOrEmpty(containerGroup))
                {
                    _links.AddLink(containerGroup, identity.Sid, "Contains");
                }
            }
        }

        void AddMembers(Identity group, string iconPath, IEnumerable<string> projectUris = null)
        {
            group = _groupSecurityService.ReadIdentity(SearchFactor.Sid, group.Sid, QueryMembership.Direct);

            foreach (var member in group.Members)
            {
                string containerGroup = null;

                var identity = _groupSecurityService.ReadIdentity(SearchFactor.Sid, member, QueryMembership.Direct);

                var nodeName = identity.DisplayName;
                var categoryName = "DefaultCollectionGroupCategory";
                var icon = iconPath;

                if (!string.IsNullOrEmpty(identity.Domain) && identity.Type == IdentityType.ApplicationGroup && identity.Domain.Contains("Framework"))
                {
                    nodeName = @"[TEAM FOUNDATION]\" + nodeName;
                    containerGroup = "TeamFoundationGroup";
                    categoryName = "TeamFoundationGroupCategory";
                    if (!AddedGroupNode.Contains(containerGroup))
                    {
                        AddedGroupNode.Add(containerGroup);
                        _nodes.AddNode(containerGroup, "Team Foundation", null, null, "Expanded");
                    }
                }
                else if (!string.IsNullOrEmpty(identity.Domain) && identity.Type == IdentityType.ApplicationGroup)
                {
                    try
                    {

                        var project = _structure.GetProject(identity.Domain);
                        if (project != null)
                        {
                            if (projectUris == null || projectUris.Contains(project.Uri))
                            {
                                containerGroup = project.Name + "Group";
                                if (!AddedGroupNode.Contains(project.Uri))
                                {
                                    AddedGroupNode.Add(project.Uri);
                                    _nodes.AddNode(project.Name + "Group", project.Name, null, null, "Expanded");
                                }
                                categoryName = "ProjectGroupSecurity";
                                nodeName = "[" + project.Name + @"]\" + nodeName;
                            }
                            else
                            {
                                continue;
                            }
                        }
                    }
                    catch
                    { }
                }
                else if (identity.Type == IdentityType.WindowsGroup || identity.Type == IdentityType.WindowsUser)
                {
                    categoryName = "DomainGroup";
                    nodeName = identity.Domain + @"\" + identity.DisplayName;
                    icon = null;
                }

                if (identity.Type == IdentityType.ApplicationGroup && string.IsNullOrEmpty(identity.Domain))
                {
                    nodeName = "[" + _collection.CatalogNode.Resource.DisplayName + @"]\" + identity.DisplayName;
                    containerGroup = _collection.CatalogNode.Resource.DisplayName + "Group";
                    if (!AddedGroupNode.Contains(_collection.CatalogNode.Resource.DisplayName))
                    {
                        AddedGroupNode.Add(_collection.CatalogNode.Resource.DisplayName);
                        _nodes.AddNode(_collection.CatalogNode.Resource.DisplayName + "Group", _collection.CatalogNode.Resource.DisplayName, null, null, "Expanded");
                    }
                }
                if (identity.Type == IdentityType.ApplicationGroup)
                {
                    AddMembers(identity, iconPath);
                }

                if (!AddedNode.Contains(identity.Sid))
                {
                    _nodes.AddNode(identity.Sid, nodeName, categoryName, icon);
                    AddedNode.Add(identity.Sid);
                }

                _links.AddLink(group.Sid, identity.Sid, "CategoryMember");

                if (!string.IsNullOrEmpty(containerGroup))
                {
                    _links.AddLink(containerGroup, identity.Sid, "Contains");
                }
            }
        }

        XDocument CreateTfsPermissionsTree()
        {
            return new XDocument(
                new XElement(DgmlNamespace + "DirectedGraph",
                             new XAttribute("GraphDirection", "LeftToRight"),
                             new XAttribute("Layout", "Sugiyama"),
                             new XElement(DgmlNamespace + "Nodes"),
                             new XElement(DgmlNamespace + "Links"),
                             new XElement(DgmlNamespace + "Categories"),
                             new XElement(DgmlNamespace + "Properties"),
                             new XElement(DgmlNamespace + "Styles")));
        }
    }

    public static class DirectedGrapheDocumentHelper
    {
        private static XNamespace dgmlNamespace = "http://schemas.microsoft.com/vs/2009/dgml";

        public static void AddNode(this XElement element, string id, string label, string category = null, string icon = null, string group = null, params Tuple<string, string>[] properties)
        {
            var node = new XElement(dgmlNamespace + "Node",
                                new XAttribute("Id", id),
                                new XAttribute("Label", label));

            if (!string.IsNullOrEmpty(category)) node.Add(new XAttribute("Category", category));
            if (!string.IsNullOrEmpty(icon)) node.Add(new XAttribute("Icon", icon));
            if (!string.IsNullOrEmpty(group)) node.Add(new XAttribute("Group", group));
            foreach (var property in properties)
            {
                node.Add(new XAttribute(property.Item1, property.Item2));
            }
            element.Add(node);
        }

        public static void AddLink(this XElement element, string sourceNodeId, string targetNodeId, string category = null)
        {
            var link = new XElement(dgmlNamespace + "Link",
                                    new XAttribute("Source", sourceNodeId),
                                    new XAttribute("Target", targetNodeId));

            if (!string.IsNullOrEmpty(category)) link.Add(new XAttribute("Category", category));

            element.Add(link);
        }

        public static void AddCategory(this XElement element, string id, string label, string canBeDataDriven = null, string canLinkedNodesBeDataDriven = null, string incomingActionLabel = null, string isContainment = null, string outgoingActionLabel = null)
        {
            var category = new XElement(dgmlNamespace + "Category",
                                    new XAttribute("Id", id),
                                    new XAttribute("Label", label));

            if (!string.IsNullOrEmpty(canBeDataDriven)) category.Add(new XAttribute("CanBeDataDriven", canBeDataDriven));
            if (!string.IsNullOrEmpty(canLinkedNodesBeDataDriven)) category.Add(new XAttribute("CanLinkedNodesBeDataDriven", canLinkedNodesBeDataDriven));
            if (!string.IsNullOrEmpty(incomingActionLabel)) category.Add(new XAttribute("IncomingActionLabel", incomingActionLabel));
            if (!string.IsNullOrEmpty(isContainment)) category.Add(new XAttribute("IsContainment", isContainment));
            if (!string.IsNullOrEmpty(outgoingActionLabel)) category.Add(new XAttribute("OutgoingActionLabel", outgoingActionLabel));

            element.Add(category);
        }

        public static XElement AddStyle(this XElement element, string groupLabel, string targetType = "Node", string valueLabel = "Has category")
        {
            var style = new XElement(dgmlNamespace + "Style",
                                new XAttribute("TargetType", targetType),
                                new XAttribute("GroupLabel", groupLabel),
                                new XAttribute("ValueLabel", valueLabel));

            element.Add(style);
            return style;
        }

        public static void AddStyleCondition(this XElement element, string expression)
        {
            element.Add(new XElement(dgmlNamespace + "Condition",
                                new XAttribute("Expression", expression)));
        }

        public static void AddStyleSetterProperty(this XElement element, string propertyName, string propertyValue)
        {
            element.Add(new XElement(dgmlNamespace + "Setter",
                                new XAttribute("Property", propertyName),
                                new XAttribute("Value", propertyValue)));
        }
    }
}
